package com.duang.framework.config;

import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.duang.framework.core.Repository;
import com.duang.framework.core.Resource;
import com.duang.framework.core.Service;
import com.duang.framework.core.TargetDao;
import com.duang.framework.core.Transactional;
import com.duang.framework.service.BaseService;
import com.duang.framework.service.TxManangerInterceptor;
import com.duang.framework.util.Asserts;
import com.duang.framework.util.IntrospectionUtils;
import com.duang.framework.util.PackageUtil;

public class BeanInjectionContainer {
	private final static Log log = LogFactory
			.getLog(BeanInjectionContainer.class);
	private List<InjectedBean> beanList = new ArrayList<InjectedBean>();
	private BeanCache beanCache;
	private String searchPackagePath;
	private Map<Class<?>, String> beanAlias = new HashMap<Class<?>, String>();
	private List<Class<?>> transactionalClazz = new ArrayList<Class<?>>();

	public BeanInjectionContainer(BeanCache beanCache, String searchPackagePath) {
		this.beanCache = beanCache;
		this.searchPackagePath = searchPackagePath;
	}

	public BeanInjectionContainer(String searchPackagePath) {
		this.beanCache = new BeanCache();
		this.searchPackagePath = searchPackagePath;

	}

	public void startInject() throws Exception {
		List<String> classNameList = PackageUtil
				.getClassName(this.searchPackagePath);
		for (String className : classNameList) {
			if (className.contains("servlet"))
				continue;
			try{
			Class<?> clazz = Class.forName(className);
			if (clazz.isAnnotationPresent(Service.class)) {
				Object object = clazz.newInstance();
				findAndAddBeanNeedToBeInjected(clazz, object);
				Service service = clazz.getAnnotation(Service.class);
				putInstanceIntoCache(beanCache, clazz, object, service.value());
			} else if (clazz.isAnnotationPresent(Repository.class)) {
				Object object = clazz.newInstance();
				findAndAddBeanNeedToBeInjected(clazz, object);
				Repository repository = clazz.getAnnotation(Repository.class);
				putInstanceIntoCache(beanCache, clazz, object,
						repository.value());
			}
			if (clazz.isAnnotationPresent(Transactional.class)) {
				transactionalClazz.add(clazz);
		    	}
			}catch(ClassNotFoundException e){
				log.warn(className+"can't be found ", e);
			}
		}
	}

	public static class InjectedBean {

		InjectedBean targetDao;

		public InjectedBean(Field field2, Object obj2, String name2) {
			this.field = field2;
			this.obj = obj2;
			this.name = name2;

		}

		
		Field field;
		Object obj;
		String name;

		@Override
		public String toString() {
			return "InjectedBean [field=" + field + ", obj=" + obj + ", name="
					+ name + "]";
		}

	}

	void injectValueToField(Field field, Object obj, Object value)
			throws InjectingBeanException {
		field.setAccessible(true);
		try {

			field.set(obj, value);

		} catch (Exception e) {
			throw new InjectingBeanException(e);
		}
	}

	public List<Class<?>> getTransactionalClassList() {
		return transactionalClazz;
	}



	public InjectedBean addBeanNeedToBeInjected(Field field, Object obj,
			String name) {
		InjectedBean injectedBean = new InjectedBean(field, obj, name);
		beanList.add(injectedBean);
		return injectedBean;
	}

	public void findAndAddBeanNeedToBeInjected(Class<?> clazz, Object object) {
		List<Field> fieldList = IntrospectionUtils.getFields(clazz, true);
		int num = 0;
		int target = 0;
		InjectedBean injectedBean = null;
		InjectedBean baseDao =null;
		if (object instanceof BaseService) {
			for (Field field : fieldList) {
				// if (SystemConfiguration.isDedug())
				System.out.println("field--->" + field);

				if (field.isAnnotationPresent(Resource.class)) {
					Resource name = field.getAnnotation(Resource.class);
					num++;
					if (field.isAnnotationPresent(TargetDao.class)) {
						if (target > 0) {
							throw new InjectingBeanException(
									"a service can't have more than one target dao");
						}
						InjectedBean bean = addBeanNeedToBeInjected(field,
								object, name.value());
					    injectedBean = bean;
						target++;

					} else {
						
						InjectedBean bean = addBeanNeedToBeInjected(field,
								object, name.value());
						if(!bean.name.equals("") && bean.name.equals("baseDao"))
						  baseDao=bean;
						if (injectedBean == null)
							injectedBean = bean;
					}
				}

			}
			if (num > 3 && target == 0) {
				throw new InjectingBeanException(
						"a service should specify a target dao by setting attribute target in annotation #Resource to true ");
			} else if (num == 2) {
				baseDao.targetDao=injectedBean;
			} else if (num == 0) {
				throw new InjectingBeanException(
						"a service should subclass the class #BaseServiceTemplate");
			}
		} else {

			for (Field field : fieldList) {
				if (SystemConfiguration.isDedug())
					System.out.println("field--->" + field);

				if (field.isAnnotationPresent(Resource.class)) {
					Resource name = field.getAnnotation(Resource.class);
					addBeanNeedToBeInjected(field, object, name.value());
				}
			}

		}
	}

	private static BeanInjectionContainer beanInjectionContainer;

	public static BeanInjectionContainer getContainer(String packagePath)
			throws Exception {
		if (beanInjectionContainer != null)
			return beanInjectionContainer;
		beanInjectionContainer = new BeanInjectionContainer(packagePath);
		beanInjectionContainer.startInject();
		beanInjectionContainer.performReallyInjection();
		return beanInjectionContainer;
	}

	public void putInstanceIntoCache(BeanCache beanCache, Class<?> clazz,
			Object obj, String beanName) throws Exception {

		Object object;
		if (obj == null)
			object = clazz.newInstance();
		else
			object = obj;
		Class<?>[] clazzs = clazz.getInterfaces();
		Queue<Class<?>> interfaceQueue = new ArrayDeque<Class<?>>(0);
		for (Class<?> c : clazzs) {
			interfaceQueue.add(c);
		}
		HashMap<Class<?>, Boolean> visitedMap = new HashMap<Class<?>, Boolean>();
		while (!interfaceQueue.isEmpty()) {
			Class<?> interfaceClazz = interfaceQueue.poll();
			if (!visitedMap.containsKey(interfaceClazz)) {
				beanCache.putInterface(interfaceClazz, object);
				visitedMap.put(interfaceClazz, Boolean.TRUE);
				for (Class<?> ifClazz : interfaceClazz.getInterfaces()) {
					interfaceQueue.add(ifClazz);
				}
			}
		}
		if (beanName != null && !beanName.equals("")) {
			beanAlias.put(clazz, beanName);
			beanCache.putNamedMap(beanName, object);
		}
		beanCache.put(clazz, object);
	}

	public void performReallyInjection() throws InjectingBeanException {

		for (int i = beanList.size() - 1; i >= 0; i--) {
			InjectedBean bean = beanList.get(i);
			if (!bean.name.equals("baseDao"))
				inject(bean);
			else
				injectValueToField(bean.field, bean.obj, findMatchedObject(bean.targetDao));

		}
	}

	private void inject(InjectedBean bean) {
	
		injectValueToField(bean.field, bean.obj, findMatchedObject(bean));

	}

	private void checkMatchedObjectSize(List<Object> matchedObjects) {
		int size;
		if ((size = matchedObjects.size()) == 1) {
			Object matchedObject = matchedObjects.get(0);
            Asserts.notNull(matchedObject, "inject bean must be not null");
		} else if (size > 1) {
			throw new InjectingBeanException("found more than one matched bean");
		} else {
			throw new InjectingBeanException("not found matched bean");
			// is equal to zero
		}
	}

	
	
	private Object findMatchedObject(InjectedBean bean) {
		if (bean.name == null || bean.name.equals("")) {
			// find type
			if (bean.field.getType().isInterface()) {
				List<Object> matchedObjects = beanCache.getInterface(bean.field
						.getType());
				checkMatchedObjectSize(matchedObjects);
				return matchedObjects.get(0);
			} else {
              Object matchedObject = beanCache.get(bean.field.getType());
			  Asserts.notNull(matchedObject,"inject bean must be not null");
			  return matchedObject;
			}

		} else {
			Object matchedObj = beanCache.getNamedObject(bean.name);
			if (matchedObj == null) {
				throw new InjectingBeanException("matched a null value--"
						+ bean.name);
			}

			return matchedObj;

		}
	}

	public BeanCache getBeanCache() {
		return beanCache;
	}

	public Map<Class<?>, String> getBeanAlias() {
		return beanAlias;
	}

}
